﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace networkTestUtilities
{
    public class DataPusher
    {
        public string host;
        public int port;
        public uint accessKey;
        public uint deviceKey;
        public int limitSize;
        public int timeout;

        private Queue<dataPoint> q = new Queue<dataPoint>();

        public bool run = true;
        public bool contMode = true;

        public void addDataPoint(int sensorId, float value, long timeStamp)
        {
            dataPoint dp = new dataPoint();
            dp.sensorId = sensorId;
            dp.sensorData = value;
            dp.timeStamp = timeStamp;

            lock (this)
            {
                q.Enqueue(dp);
            }
        }

        public void start()
        {
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(runThread));
            t.Start();
        }

        private void runThread(object obj)
        {
            while (run)
            {

                System.Net.Sockets.TcpClient tc1;
                System.Net.Sockets.NetworkStream n;

                while (q.Count == 0)
                {
                    System.Console.WriteLine("Empty Queue. Sleep");
                    System.Threading.Thread.Sleep(1000);
                }

                
                try
                {
                    tc1 = new System.Net.Sockets.TcpClient();
                    tc1.Connect(host, port);
                    n = tc1.GetStream();
                    n.ReadTimeout = -1;
                }
                catch (Exception ex)
                {
                    System.Console.WriteLine("TCP Communication Error. Queue size: " + q.Count());
                    System.Threading.Thread.Sleep(100);
                    continue;
                }

                authRequest ar = new authRequest();
                ar.accessKey = accessKey;
                ar.deviceKey = deviceKey;

                byte[] reqPacket = ar.getStream();

                try
                {
                    n.Write(reqPacket, 0, reqPacket.Length);
                    Console.WriteLine("Writing auth request");
                    bool success = false;
                    while (!success)
                    {
                        int byteVal = n.ReadByte();
                        if (byteVal == 1)
                        {
                            n.ReadByte();
                            success = true;
                            Console.WriteLine("Auth successful.");
                        }
                        else if (byteVal == 0)
                        {
                            Console.WriteLine("Auth failed. Closing connection");
                            tc1.Close();
                            System.Threading.Thread.Sleep(5000);
                        }
                        else
                        {
                            Console.WriteLine("No data availiable");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Authentication TCP transport error.");
                    continue;
                }


                while (tc1.Connected == true)
                {
                    //Console.Clear();
                    System.Console.WriteLine("Success. Queue size: " + q.Count());
                    if (q.Count() > 0)
                    {

                        dataPacket dp = new dataPacket();
                        List<dataPoint> tempQueue = new List<dataPoint>();
                        lock (this)
                        {
                            dp.numDataObjs = (ushort)Math.Min(q.Count(), this.limitSize);

                            dataPoint dpt = q.Peek();

                            dp.masterTimeStamp = (uint)(dpt.timeStamp / 1000);
                            dp.dataObjs = new dataObj[dp.numDataObjs];

                            for (int i = 0; i < dp.numDataObjs; i++)
                            {
                                dpt = q.Dequeue();
                                tempQueue.Add(dpt);
                                dp.dataObjs[i] = new dataObj();
                                dp.dataObjs[i].deltaTimeStamp = (uint)(dpt.timeStamp - dp.masterTimeStamp);
                                dp.dataObjs[i].sensorData = dpt.sensorData;
                                dp.dataObjs[i].sensorId = (byte)dpt.sensorId;
                            }
                        }

                        byte[] dataPayload = dp.getStream();

                        //Debug to print the byte stream
                        //for (int i = 0; i < dataPayload.Length; i++)
                        //    Console.Write(dataPayload[i] + " ");
                        //Console.WriteLine();

                        try
                        {
                            n.Write(dataPayload, 0, dataPayload.Length);
                            n.ReadTimeout = 500;
                            int val = n.ReadByte();
                            if (val == -1)
                                throw new Exception("Server did not respond quick enough. Retransmitting");

                            if (val != 1) // something bad happened!
                            {
                                n.ReadTimeout = 10;
                                byte [] shortBuffer = new byte[2];
                                if (n.Read(shortBuffer, 0, 2) != 2) throw new Exception("Server did not response quick enough.");
                                Array.Reverse(shortBuffer);
                                int numBytes = BitConverter.ToUInt16(shortBuffer, 0);
                                for (int i = 0; i < numBytes; i++)
                                {
                                    if (n.Read(shortBuffer, 0, 2) != 2) throw new Exception("Server did not response quick enough.");
                                    Array.Reverse(shortBuffer);
                                    int failedIndex = BitConverter.ToUInt16(shortBuffer, 0);
                                    Console.WriteLine("Sequence ID " + failedIndex + " is corrupt");
                                    lock (this)
                                    {
                                        q.Enqueue(tempQueue[failedIndex]);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            System.Console.WriteLine("Data Transmission Failure. Queue size: " + q.Count());
                            System.Console.WriteLine(ex.Message);
                            lock (this)
                            {
                                foreach (dataPoint dp1 in tempQueue)
                                    q.Enqueue(dp1);

                            }

                            if (tc1.Connected == true)
                                tc1.Close();

                            break;
                        }


                    }
                    else
                    {
                        System.Threading.Thread.Sleep(100);
                        Console.WriteLine("Sleep.");
                    }

                    if (this.contMode == false)
                    {
                        Console.WriteLine("Closing socket.");
                        tc1.Close();
                        break;
                    }
                }


            }

        }

    }

    class dataPoint
    {
        public long timeStamp;
        public float sensorData;
        public int sensorId;
    }


    class authRequest
    {
        public uint accessKey;
        public uint deviceKey;
        public int timeStamp;

        public byte[] getStream()
        {
            byte[] output = new byte[12];

            byte[] accessKeyBytes = BitConverter.GetBytes(accessKey);
            byte[] deviceKeyBytes = BitConverter.GetBytes(deviceKey);
            byte[] timeStampBytes = BitConverter.GetBytes(timeStamp);

            Array.Reverse(accessKeyBytes);
            Array.Reverse(deviceKeyBytes);
            Array.Reverse(timeStampBytes);


            Array.Copy(accessKeyBytes, 0, output, 0, 4);
            Array.Copy(deviceKeyBytes, 0, output, 4, 4);
            Array.Copy(timeStampBytes, 0, output, 8, 4);
            return output;
        }
    }

    class dataPacket
    {
        public uint masterTimeStamp;
        public ushort numDataObjs;
        public dataObj[] dataObjs;

        private byte[] reverse(byte[] input)
        {
            Array.Reverse(input);
            return input;
        }

        public byte[] getStream()
        {
            byte[] output = new byte[6 + numDataObjs * 12];
            int outputIndex = 0;

            Array.Copy(reverse(BitConverter.GetBytes(masterTimeStamp)), 0, output, 0, 4);
            outputIndex += 4;
            Array.Copy(reverse(BitConverter.GetBytes(numDataObjs)), 0, output, 4, 2);
            outputIndex += 2;
            for (int i = 0; i < numDataObjs; i++)
            {
                byte[] dataObjBytes = new byte[12];
                Array.Copy(reverse(BitConverter.GetBytes((ushort)i)), 0, dataObjBytes, 0, 2);
                dataObjBytes[2] = dataObjs[i].sensorId;
                //Array.Copy(reverse(BitConverter.GetBytes(dataObjs[i].sensorId)), 0, dataObjBytes, 2, 1);
                Array.Copy(reverse(BitConverter.GetBytes(dataObjs[i].deltaTimeStamp)), 0, dataObjBytes, 4, 4);
                Array.Copy(reverse(BitConverter.GetBytes(dataObjs[i].sensorData)), 0, dataObjBytes, 8, 4);
                dataObjBytes[3] = 0;
                int checksum = 0;
                for (int j = 0; j < 12; j++)
                {
                    checksum += dataObjBytes[j];
                }
                dataObjBytes[3] = (byte)(checksum % 256);

                Array.Copy(dataObjBytes, 0, output, outputIndex, 12);
                outputIndex += 12;
            }

            return output;
        }


    }

    class dataObj
    {
        public uint deltaTimeStamp;
        public byte sensorId;
        public float sensorData;
    }

}
